{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Layouts;

interface

uses Classes, UIObjects, XMLHelper;

{$IFDEF DESIGNER}
const
  MODIFY_CAPTION = 'Edit Positions';

  // Property categories and hints for designer
  CAT_LAYOUT = 'Layout';
  CAT_GRID = 'Grid';
  HINT_LAYOUTTYPE = 'specifies the type of layout (grid or custom positions)';
  HINT_POSITIONS = 'defines a list of custom positions within the layout';
  HINT_COLUMNCOUNT = 'defines the number of columns for a grid layout';
  HINT_ROWCOUNT = 'defines the number of rows for a grid layout';
  HINT_ROWHEIGHT = 'specifies the height for all rows in the grid layout';
  HINT_COLUMNWIDTH = 'specifies the width for all columns in the grid layout';
  HINT_ROWHEIGHTS = 'defines a list of custom row heights in the grid layout';
  HINT_COLUMNWIDTHS = 'defines a list of custom column widths in the grid layout';
{$ENDIF}

type
  TAHMLayoutType = (ltGrid, ltCustom);

  TAHMLayoutPosition = class(TAHMUIObject)
  private
    FX, FY: Single;
    FVirtX, FVirtY: Integer;
  protected
    procedure SetVirtualX(X: Integer);
    procedure SetVirtualY(Y: Integer);
    procedure UpdateName;
  public
    constructor Create(const Name: String); override;
    property X: Single read FX;
    property Y: Single read FY;
    procedure Assign(Source: TPersistent); override;
    procedure SaveAsXML(Lines: TStrings); override;
  published
    property VirtualX: Integer read FVirtX write SetVirtualX;
    property VirtualY: Integer read FVirtY write SetVirtualY;
  end;

  TAHMLayoutPositionList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function Modify: Boolean; override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
  end;

  TAHMLayout = class(TAHMUIObject)
  private
    FType: TAHMLayoutType;
    FPositionList: TAHMLayoutPositionList;
    FRowCount: Integer;
    FColumnCount: Integer;
    FColumnWidth: Single;
    FVirtColumnWidth: Integer;
    FRowHeight: Single;
    FVirtRowHeight: Integer;
    FCurrentPosition: Integer;
  protected
    procedure SetLayoutType(LayoutType: TAHMLayoutType);
    procedure SetColumnCount(Count: Integer);
    procedure SetRowCount(Count: Integer);
    procedure SetVirtualColumnWidth(Width: Integer);
    procedure SetVirtualRowHeight(Height: Integer);
    procedure ClearProperties; override;
  public
    constructor Create(const Name: String); override;
    destructor Destroy; override;
    property ColumnWidth: Single read FColumnWidth write FColumnWidth;
    property RowHeight: Single read FRowHeight write FRowHeight;
    property CurrentPosition: Integer read FCurrentPosition write FCurrentPosition;
    function GetPosition(Index: Integer): TAHMLayoutPosition;
    function GetGridCoordinate(Row, Column: Integer; var X, Y: Single): Boolean;
    function RenderPreview: Boolean; override;
    function ModifyActions: Integer; override;
    function Modify(Action: Integer): Boolean; override;
    function ModifyCaption(Action: Integer): String; override;
    procedure SeedObject; override;
    procedure Assign(Source: TPersistent); override;
    procedure SaveXMLBody(Lines: TStrings); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
    procedure InvalidateUIObject(UIObject: TAHMUIObject); override;
    procedure BuildProperties(Properties: TAHMUIPropertyList); override;
  published
    property LayoutType: TAHMLayoutType read FType write SetLayoutType;
    property ColumnCount: Integer read FColumnCount write SetColumnCount;
    property RowCount: Integer read FRowCount write SetRowCount;
    property VirtualRowHeight: Integer read FVirtRowHeight write SetVirtualRowHeight;
    property VirtualColumnWidth: Integer read FVirtColumnWidth write SetVirtualColumnWidth;
    property PositionList: TAHMLayoutPositionList read FPositionList;
  end;

  TAHMLayoutLocation = class(TPersistent)
  private
    FLayout: TAHMLayout;
    FRow, FColumn, FPosition, FCurrentIndex: Integer;
  protected
    function GetDesignerPos: String;
    procedure SetDesignerPos(Position: String);
    procedure SetLayout(Layout: TAHMLayout);
    procedure SetRow(Row: Integer);
    procedure SetColumn(Column: Integer);
    procedure SetPosition(Position: Integer);
  public
    function AllocatePosition(var X, Y: Single): Boolean;
    function AdjustLayout(DX, DY: Integer): Boolean;
    function Equals(LayoutLocation: TAHMLayoutLocation): Boolean;
    procedure Assign(Source: TPersistent); override;
    procedure Clear;
  published
    property Layout: TAHMLayout read FLayout write SetLayout;
    property Row: Integer read FRow write SetRow;
    property Column: Integer read FColumn write SetColumn;
    property Position: Integer read FPosition write SetPosition;
    property DesignerPos: String read GetDesignerPos write SetDesignerPos;
  end;

  TAHMLayoutList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddLayout(const InheritFrom, NewName: String): TAHMLayout;
    function GetLayout(const Name: String): TAHMLayout;
    procedure ResetLayouts;
  end;


implementation

uses SysUtils, ApplicationManager, Screen, Canvas, Colour, TagNames, XMLTags,
     {$IFDEF DESIGNER}DesignerList,{$ENDIF} ErrorCodes, AHMTypes, Logging,
     Utilities;


constructor TAHMLayoutPosition.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_POSITION;
  ImageIndex := Ord(otPlaces);
end;

procedure TAHMLayoutPosition.SetVirtualX(X: Integer);
begin
  FVirtX := X;
  FX := ScreenInstance.ScaleVirtualX(X);
  UpdateName;
end;

procedure TAHMLayoutPosition.SetVirtualY(Y: Integer);
begin
  FVirtY := Y;
  FY := ScreenInstance.ScaleVirtualY(Y);
  UpdateName;
end;

procedure TAHMLayoutPosition.UpdateName;
begin
  Rename('X=' + IntToStr(FVirtX) + ', Y=' + IntToStr(FVirtY));
end;

procedure TAHMLayoutPosition.Assign(Source: TPersistent);
begin
  inherited;

  with Source as TAHMLayoutPosition do
  begin
    Self.VirtualX := VirtualX;
    Self.VirtualY := VirtualY;
  end;
end;

procedure TAHMLayoutPosition.SaveAsXML(Lines: TStrings);
var
  sWork: String;
begin
  // Override standard XML layout to include comment prefix on same line
  sWork := TGB+TAG_POSITION+TGC+ IntToStr(FVirtX) + ',' +
           IntToStr(FVirtY) + TGE+TAG_POSITION+TGC;
  if Comment <> '' then sWork := FormatXMLComment(Comment) + sWork;
  Lines.Add(TABS[4] + sWork);
end;

//----------------------------------------------------------------------------//

constructor TAHMLayoutPositionList.Create;
begin
  inherited Create;

  XMLTagName := TAG_CUSTOM;
  XMLIndent := 2;
  FSpaceElements := False;
  ChildClass := TAHMLayoutPosition;
  DefaultSort := lsNatural;
end;

function TAHMLayoutPositionList.Modify: Boolean;
begin
{$IFDEF DESIGNER}
  // Edit layout positions with list dialog
  Result := TfrmObjectList.ShowList(Self, nil, nil);
  if Result then Owner.InvalidateUIObject(TAHMUIObject(Self));
{$ELSE}
  Result := False;
{$ENDIF}
end;

procedure TAHMLayoutPositionList.ParseFromXML(XML: TAHMXMLHelper);
var
  iX, iY: Integer;
  Position: TAHMLayoutPosition;
  RootNode, ItemNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  RootNode := XML.CurrentNode;
  ItemNode := nil;

  // Read list of custom positions
  while XML.FindElement(RootNode, ItemNode, TAG_POSITION) do
  begin
    if not XML.GetCoordsValue(iX, iY) then
      LogParsingError(THM_PRS_INT_MALFORMED_LY_POSITION_TAGS, Self);
    Position := TAHMLayoutPosition.Create('');
    Position.VirtualX := iX;
    Position.VirtualY := iY;
    Position.Comment := XML.CurrentComment;
    Add(Position);
  end;
end;

//----------------------------------------------------------------------------//

constructor TAHMLayoutList.Create;
begin
  inherited Create;

  XMLTagName := TAG_LAYOUTS;
  ChildClass := TAHMLayout;
end;

function TAHMLayoutList.AddLayout(const InheritFrom, NewName: String): TAHMLayout;
begin
  Result := TAHMLayout(Inherit(InheritFrom, NewName));
end;

function TAHMLayoutList.GetLayout(const Name: String): TAHMLayout;
begin
  Result := TAHMLayout(GetObject(Name));
end;

procedure TAHMLayoutList.ResetLayouts;
var
  i: Integer;
begin
  // Reset position allocation for all layouts
  for i := 0 to Pred(TotalCount) do
    TAHMLayout(GetObject(i)).CurrentPosition := 0;
end;

//----------------------------------------------------------------------------//
constructor TAHMLayout.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_LAYOUT;
  ObjectType := otLayouts;
  ImageIndex := Ord(otLayouts);
//  IsPreviewable := True;

  FPositionList := TAHMLayoutPositionList.Create;
  FPositionList.Owner := Self;
end;

destructor TAHMLayout.Destroy;
begin
  FPositionList.FreeWithContents;

  inherited Destroy;
end;

procedure TAHMLayout.SetLayoutType(LayoutType: TAHMLayoutType);
begin
  if FType <> LayoutType then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FType := LayoutType;
end;

procedure TAHMLayout.SetColumnCount(Count: Integer);
begin
  if FColumnCount <> Count then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FColumnCount := Count;
end;

procedure TAHMLayout.SetRowCount(Count: Integer);
begin
  if FRowCount <> Count then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FRowCount := Count;
end;

procedure TAHMLayout.SetVirtualColumnWidth(Width: Integer);
begin
  if Width <> FVirtColumnWidth then
  begin
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
    FVirtColumnWidth := Width;
    FColumnWidth := ScreenInstance.ScaleVirtualX(Width);
  end;
end;

procedure TAHMLayout.SetVirtualRowHeight(Height: Integer);
begin
  if Height <> FVirtRowHeight then
  begin
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
    FVirtRowHeight := Height;
    FRowHeight := ScreenInstance.ScaleVirtualY(Height);
  end;
end;

function TAHMLayout.GetPosition(Index: Integer): TAHMLayoutPosition;
begin
  if (Index >= 0) and (Index < FPositionList.Count) then
    Result := TAHMLayoutPosition(FPositionList.Objects[Index])
  else
    Result := nil;
end;

function TAHMLayout.GetGridCoordinate(Row, Column: Integer; var X, Y: Single): Boolean;
begin
  Result := False;

  // Do we have any columns?
  if FColumnCount = 0 then
    Result := True
  else
    if (Column >= 0) and (Column < FColumnCount) then
    begin
      X := Column * FColumnWidth;

      // Okay, coordinate allocated successfully
      Result := True;
    end;

  // Do we have any rows?
  if FRowCount = 0 then
    Result := Result and True
  else
    if (Row >= 0) and (Row < FRowCount) then
    begin
      Y := Row * FRowHeight;

      // Okay, coordinate allocated successfully
      Result := Result and True;
    end;
end;

procedure TAHMLayout.ClearProperties;
begin
  inherited;

  // Clear custom layout properties
  FType := ltGrid;
  FPositionList.ClearObjects;
  VirtualColumnWidth := 0;
  VirtualRowHeight := 0;
  FCurrentPosition := 0;
end;

function TAHMLayout.RenderPreview: Boolean;
{$IFDEF DESIGNER}
var
  i, x, y: Integer;
  Offset: Single;
{$ENDIF}
begin
{$IFDEF DESIGNER}
  with ScreenInstance.Viewport do
  begin
    Canvas.DesignerBackground(ViewportRect);

    case FType of
      ltCustom:
        // Render custom positions
        for i := 0 to Pred(FPositionList.Count) do
          with GetPosition(i) do
            Canvas.DesignerCrossHair(X, Y, Width / 20, Height / 20);
      else
        // Render grid positions
        for x := 0 to Pred(FColumnCount) do
        begin
          Offset := x * FColumnWidth;
          for y := 0 to Pred(FRowCount) do
            Canvas.DesignerCrossHair(Offset, y * FRowHeight, Width / 20, Height / 20);
        end;
    end;
  end;
{$ENDIF}
  Result := True;
end;

function TAHMLayout.ModifyActions: Integer;
begin
  // Layout supports a single action to edit positions
  Result := 1;
end;

function TAHMLayout.Modify(Action: Integer): Boolean;
begin
{$IFDEF DESIGNER}
  // Edit positions with list dialog
  Result := FPositionList.Modify;
  if Result then DoModified;
{$ELSE}
  Result := False;
{$ENDIF}
end;

function TAHMLayout.ModifyCaption(Action: Integer): String;
begin
{$IFDEF DESIGNER}
  Result := MODIFY_CAPTION;
{$ELSE}
  Result := '';
{$ENDIF}
end;

procedure TAHMLayout.SeedObject;
begin
{$IFDEF DESIGNER}
  if Ancestor = nil then
  begin
    RowCount := 5;
    ColumnCount := 5;
    VirtualRowHeight := 100;
    VirtualColumnWidth := 100;
  end;
{$ENDIF}
end;

procedure TAHMLayout.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMLayout do
  begin
    Self.LayoutType := LayoutType;
    Self.ColumnCount := ColumnCount;
    Self.RowCount := RowCount;
    Self.VirtualColumnWidth := VirtualColumnWidth;
    Self.VirtualRowHeight := VirtualRowHeight;
    Self.PositionList.AssignContents(PositionList);
  end;
end;

procedure TAHMLayout.SaveXMLBody(Lines: TStrings);
var
  sWork: String;
  oAncs: TAHMLayout;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMLayout(ObjectToCompare);

  // Only populate attributes that differ from our ancestor
  if FType = ltGrid then
  begin
    // Add grid layout type element
    sWork := TGB+TAG_GRID;
    if RowCount <> 0 then
      sWork := sWork + SPC+ATTR_ROWS+ATB + IntToStr(RowCount) + ATQ;
    if ColumnCount <> 0 then
      sWork := sWork + SPC+ATTR_COLS+ATB + IntToStr(ColumnCount) + ATQ;
    Lines.Add(TABS[3] + sWork + TGC);

    // Add column width if different from ancestor
    if (ColumnCount <> 0) and (FVirtColumnWidth <> 0) and
       (FVirtColumnWidth <> oAncs.VirtualColumnWidth) then
      Lines.Add(TABS[4] + TGB+TAG_COLUMNS+SPC+ATTR_WIDTH+ATB +
                IntToStr(FVirtColumnWidth) + ATE);

    // Add row height if different from ancestor
    if (RowCount <> 0) and (FVirtRowHeight <> 0) and
       (FVirtRowHeight <> oAncs.VirtualRowHeight) then
      Lines.Add(TABS[4] + TGB+TAG_ROWS+SPC+ATTR_HEIGHT+ATB +
                IntToStr(FVirtRowHeight) + ATE);

    Lines.Add(TABS[3] + TGE+TAG_GRID+TGC);
  end
  else
  begin
    // Add list of custom layout positions
    FPositionList.SaveDiffsAsXML(oAncs.PositionList, Lines);
  end;
end;

procedure TAHMLayout.ParseFromXML(XML: TAHMXMLHelper);
var
  sRows, sCols, sHeight, sWidth: String;
  iWidth, iHeight: Integer;
  LayoutNode, PropsNode, DetailNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  LayoutNode := XML.CurrentNode;
  PropsNode := nil;

  // Multiple layout types are possible, so see what we have
  while XML.NextElement(LayoutNode, PropsNode) do
  begin
    if XML.CurrentElement = TAG_CUSTOM then
    begin
      LayoutType := ltCustom;

      // Read list of custom positions
      FPositionList.ParseFromXML(XML);
    end
    else if XML.CurrentElement = TAG_GRID then
    begin
      LayoutType := ltGrid;
      DetailNode := nil;

      // Set row/column count for grid layout
      sRows := XML.GetAttribute(ATTR_ROWS);
      sCols := XML.GetAttribute(ATTR_COLS);
      if sRows <> '' then RowCount := StrToInt(sRows);
      if sCols <> '' then ColumnCount := StrToInt(sCols);

      // Reset total width and height for legacy layouts
      iWidth := 0; iHeight := 0;

      // Row & column lists are optional, so see what we have
      while XML.NextElement(PropsNode, DetailNode) do
      begin
        if XML.CurrentElement = TAG_COLUMNS then
        begin
          sWidth := XML.GetAttribute(ATTR_WIDTH);
          if sWidth <> '' then VirtualColumnWidth := StrToInt(sWidth);
        end
        else if XML.CurrentElement = TAG_ROWS then
        begin
          sHeight := XML.GetAttribute(ATTR_HEIGHT);
          if sHeight <> '' then VirtualRowHeight := StrToInt(sHeight);
        end
        else if XML.CurrentElement = TAG_COLUMN then
          Inc(iWidth, StrToIntDef(XML.GetAttribute(ATTR_WIDTH), 0))
        else if XML.CurrentElement = TAG_ROW then
          Inc(iHeight, StrToIntDef(XML.GetAttribute(ATTR_HEIGHT), 0))
      end;

      // If legacy individual row/column sizes are set then take average
      if iWidth <> 0 then VirtualColumnWidth := iWidth div ColumnCount;
      if iHeight <> 0 then VirtualRowHeight := iHeight div RowCount;
    end
  end;
end;

procedure TAHMLayout.InvalidateUIObject(UIObject: TAHMUIObject);
begin
  // Invalidate layout if position list changes
  if TAHMUIObjectList(UIObject) = FPositionList then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
end;

procedure TAHMLayout.BuildProperties(Properties: TAHMUIPropertyList);
begin
{$IFDEF DESIGNER}
  inherited;

  // Build custom layout properties
  Properties.Add('Type', 'LayoutType', CAT_LAYOUT, HINT_LAYOUTTYPE);
  Properties.Add('Positions', 'PositionList', CAT_LAYOUT, HINT_POSITIONS);
  Properties.Add('Columns', 'ColumnCount', CAT_GRID, HINT_COLUMNCOUNT);
  Properties.Add('Rows', 'RowCount', CAT_GRID, HINT_ROWCOUNT);
  Properties.Add('Row Height', 'VirtualRowHeight', CAT_GRID, HINT_ROWHEIGHT);
  Properties.Add('Column Width', 'VirtualColumnWidth', CAT_GRID, HINT_COLUMNWIDTH);
{$ENDIF}
end;

//----------------------------------------------------------------------------//

function TAHMLayoutLocation.GetDesignerPos: String;
var
  oPosition: TAHMLayoutPosition;
begin
  // Set default result - not supported
  Result := '';

  // Return current X/Y location in concatenated string format
  if FLayout.LayoutType = ltGrid then
    Result := Format('%d,%d', [FLayout.VirtualRowHeight, FLayout.VirtualColumnWidth])
  else if FLayout.LayoutType = ltCustom then
  begin
    oPosition := FLayout.GetPosition(FCurrentIndex);
    if oPosition <> nil then
      Result := Format('%d,%d', [oPosition.VirtualX, oPosition.VirtualY]);
  end;
end;

procedure TAHMLayoutLocation.SetDesignerPos(Position: String);
var
  X, Y: Integer;
  oPosition: TAHMLayoutPosition;
begin
  // Parse concatenated string format to derive new X/Y location
  if ParseStringValues(Position, X, Y) then
    if FLayout.LayoutType = ltGrid then
    begin
      FLayout.VirtualRowHeight := X;
      FLayout.VirtualColumnWidth := Y;
    end
    else if FLayout.LayoutType = ltCustom then
    begin
      oPosition := FLayout.GetPosition(FCurrentIndex);
      if oPosition <> nil then
      begin
        oPosition.VirtualX := X;
        oPosition.VirtualY := Y;
        ApplicationManagerInstance.UIManager.InvalidateUIObject(FLayout);
      end;
    end;
end;

procedure TAHMLayoutLocation.SetLayout(Layout: TAHMLayout);
begin
  if FLayout <> Layout then
  begin
    // This change invalidates both the old and new layouts
    ApplicationManagerInstance.UIManager.InvalidateUIObject(FLayout);
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Layout);
  end;
  FLayout := Layout;
end;

procedure TAHMLayoutLocation.SetRow(Row: Integer);
begin
  if FRow <> Row then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(FLayout);
  FRow := Row;
end;

procedure TAHMLayoutLocation.SetColumn(Column: Integer);
begin
  if FColumn <> Column then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(FLayout);
  FColumn := Column;
end;

procedure TAHMLayoutLocation.SetPosition(Position: Integer);
begin
  if FPosition <> Position then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(FLayout);
  FPosition := Position;
end;

function TAHMLayoutLocation.AllocatePosition(var X, Y: Single): Boolean;
var
  iRow, iCol: Integer;
  oPosition: TAHMLayoutPosition;
begin
  Result := False;

  with Layout do
  begin
    // Position allocation method depends on layout type
    if LayoutType = ltGrid then
    begin
      // Allocate unspecified control locations from top left corner
      // working across then down (like reading a book).
      if Row <> 0 then
        iRow := Pred(Row)
      else
        if ColumnCount > 0 then
          iRow := CurrentPosition div ColumnCount
        else
          iRow := CurrentPosition;

      if Column <> 0 then
        iCol := Pred(Column)
      else
        if ColumnCount > 0 then
          iCol := CurrentPosition mod ColumnCount
        else
          iCol := CurrentPosition;

      // Increment current position if necessary
      if ((ColumnCount = 0) or (Column = 0)) or
         ((RowCount = 0) or (Row = 0)) then
        CurrentPosition := CurrentPosition + 1;

      // Get top left coordinate of relevant row/column
      Result := GetGridCoordinate(iRow, iCol, X, Y);

      if LogDebugEnabled then
        LogDebug('Calculated layout Col=' + IntToStr(iCol) + ', Row=' +
          IntToStr(iRow) + ', X=' + FloatToStr(X) + ', Y=' + FloatToStr(Y));
    end
    else
    begin
      // Was a preferred position specified?
      if Position <> 0 then
        FCurrentIndex := Pred(Position)
      else
      begin
        // Allocate next custom position available
        FCurrentIndex := CurrentPosition;
        CurrentPosition := CurrentPosition + 1;
      end;

      // Return allocated custom position
      oPosition := GetPosition(FCurrentIndex);
      if oPosition <> nil then
      begin
        X := oPosition.X;
        Y := oPosition.Y;

        // Okay, allocated position
        Result := True;
      end;
    end;
  end;
end;

function TAHMLayoutLocation.AdjustLayout(DX, DY: Integer): Boolean;
var
  oPosition: TAHMLayoutPosition;
begin
  // Set default result - not changed
  Result := False;

  if Assigned(FLayout) and ((DX <> 0) or (DY <> 0)) then
    if FLayout.LayoutType = ltGrid then
    begin
      // Tweak default row/column sizes
      FLayout.VirtualRowHeight := FLayout.VirtualRowHeight + DY;
      FLayout.VirtualColumnWidth := FLayout.VirtualColumnWidth + DX;
      Result := True;
    end
    else if FLayout.LayoutType = ltCustom then
    begin
      oPosition := FLayout.GetPosition(FCurrentIndex);
      if oPosition <> nil then
      begin
        // Tweak relevant custom position & invalidate layout
        oPosition.VirtualX := oPosition.VirtualX + DX;
        oPosition.VirtualY := oPosition.VirtualY + DY;
        ApplicationManagerInstance.UIManager.InvalidateUIObject(FLayout);
        Result := True;
      end;
    end;
end;

function TAHMLayoutLocation.Equals(LayoutLocation: TAHMLayoutLocation): Boolean;
begin
  with LayoutLocation do
    Result := (Layout = Self.Layout) and (Position = Self.Position) and
              (Row = Self.Row) and (Column = Self.Column);
end;

procedure TAHMLayoutLocation.Assign(Source: TPersistent);
begin
  with Source as TAHMLayoutLocation do
  begin
    Self.Layout := Layout;
    Self.Position := Position;
    Self.Row := Row;
    Self.Column := Column;
  end;
end;

procedure TAHMLayoutLocation.Clear;
begin
  // Clear custom layout properties
  FLayout := nil;
  FRow := 0;
  FColumn := 0;
  FPosition := 0;
end;

end.
